using System;
using System.Diagnostics;
using System.IO;
using SqlScriptsRunner.Properties;

namespace SqlScriptsRunner
{
    internal sealed class ScriptRunner
    {
        #region Fields

        private string[] _args;
        private static Settings _config;
        private readonly ProcessStartInfo _startInfo;
        private readonly string _serverName;
        private const string DefaultSqlServerName = @".\SQL2008R2";
        private const string DefaultSqlDirPath = @"D:\inetpub\Database\";
        private const string SqlFileShearchPattern = "*.SQL";
        private const string SqlCmdFileName = "sqlcmd";
        private const string SqlCmdExecutePattern = "-S {0} -i {1}";

        #endregion

        #region Properties

        public bool ProcessAll { get { return (_args == null || _args.Length == 0); } }

        #endregion

        #region Constructor

        public ScriptRunner()
        {
            InitConfig();
            // full sqlcmd util info: http://msdn.microsoft.com/ru-ru/library/e1728707-5215-4c04-8320-e36f161b834a
            _startInfo = new ProcessStartInfo(SqlCmdFileName) { CreateNoWindow = true };
            _serverName = string.IsNullOrWhiteSpace(_config.SqlServerName) ? DefaultSqlServerName : _config.SqlServerName;
        }

        #endregion

        #region Methods

        internal void BeepComplete()
        {
            Console.Beep(500, 200);
            Console.Beep(550, 200);
            Console.Beep(600, 200);
            Console.Beep(650, 200);
            Console.Beep(700, 200);
            Console.Beep(750, 200);
            Console.Beep(500, 500);
            Console.Beep(400, 500);
        }

        internal bool ProcessScripts(string[] args)
        {
            _args = args;

            if (!ProcessAll)
            {
                if (string.Equals("?", _args[0], StringComparison.InvariantCultureIgnoreCase))
                {
                    DisplayHelp();
                    return false;
                }
                if (string.Equals("reset", _args[0], StringComparison.InvariantCultureIgnoreCase))
                {
                    ResetSettings();
                    return false;
                }
            }

            var pathToProcess = string.IsNullOrWhiteSpace(_config.BasePath) ? DefaultSqlDirPath : _config.BasePath;

            return ProcessDirectory(ProcessAll ? pathToProcess : _args[0]);
        }
        
        #region Settings Methods

        private static void InitConfig()
        {
            try
            {
                _config = Settings.Default;
                var s = _config.SqlServerName;
                s.TrimEnd(); 
            }
            catch (Exception ex)
            {
                WriteLine(string.Format("\nError loading configuration file.\nError:\n{0}\n", ex.Message), ConsoleColor.Red);
                ResetSettings();
            }
        }

        private static void ResetSettings()
        {
            const string defaultSettingsFileText = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<configuration>\r\n    <configSections>\r\n        <sectionGroup name=\"applicationSettings\" type=\"System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" >\r\n            <section name=\"SqlScriptsRunner.Properties.Settings\" type=\"System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" requirePermission=\"false\" />\r\n        </sectionGroup>\r\n    </configSections>\r\n    <applicationSettings>\r\n        <SqlScriptsRunner.Properties.Settings>\r\n            <setting name=\"BasePath\" serializeAs=\"String\">\r\n                <value>D:\\inetpub\\Database\\</value>\r\n            </setting>\r\n            <setting name=\"PriorityDb\" serializeAs=\"Xml\">\r\n                <value>\r\n                    <ArrayOfString xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n                        xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\r\n                        <string>SystemDB</string>\r\n                        <string>Config</string>\r\n                        <string>ExactTarget</string>\r\n                        <string>SysQueue</string>\r\n                        <string>Auth</string>\r\n                        <string>App</string>\r\n                        <string>AppLog</string>\r\n                        <string>AppSession</string>\r\n                    </ArrayOfString>\r\n                </value>\r\n            </setting>\r\n            <setting name=\"PriorityDirs\" serializeAs=\"Xml\">\r\n                <value>\r\n                    <ArrayOfString xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n                        xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\r\n                        <string>Tables</string>\r\n                        <string>Views</string>\r\n                        <string>Triggers</string>\r\n                        <string>ServiceBroker</string>\r\n                        <string>Procedures</string>\r\n                        <string>Functions</string>\r\n                        <string>Data</string>\r\n                    </ArrayOfString>\r\n                </value>\r\n            </setting>\r\n            <setting name=\"SqlServerName\" serializeAs=\"String\">\r\n                <value>.\\SQLEXPRESS</value>\r\n            </setting>\r\n            <setting name=\"UseOnlyRegisteredFolders\" serializeAs=\"String\">\r\n                <value>False</value>\r\n            </setting>\r\n        </SqlScriptsRunner.Properties.Settings>\r\n    </applicationSettings>\r\n</configuration>";
            try
            {
                File.WriteAllText(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, defaultSettingsFileText);
                WriteLine(string.Format("Settings reseted to default values."), ConsoleColor.Magenta);
            }
            catch (Exception ex)
            {
                WriteLine(ex.Message, ConsoleColor.Red);
            }
        }
        
        #endregion
        
        private static void DisplayHelp()
        {
            Console.WriteLine("Genetral SQL Script Runner help:\n");
            Console.WriteLine("  ? - this help\n");
            Console.WriteLine("  [any other string] - directory path, with SQL scripts included, to progess\n");
            Console.WriteLine("  [no parameters] - recursive process \"D:\\inetpub\\Database\\\" directory\n");
            Console.WriteLine("  reset - reset the app settings to default values\n");
        }

        #region Private Methods

        private bool ProcessDirectory(string directoryPath)
        {
            bool result = false;

            if (!Directory.Exists(directoryPath))
            {
                Write("\n\nDirectory ", ConsoleColor.Red);
                Write(string.Format("\"{0}\"", directoryPath), ConsoleColor.Yellow);
                Write(" is not exists.\n", ConsoleColor.Red);

                return false;
            }

            try
            {
                WriteLine(string.Format("____________________________________________________________\nStart processing \"{0}\" directory.", directoryPath), ConsoleColor.Yellow);

                var dirs = Directory.GetDirectories(directoryPath);
                if (dirs.Length > 0)
                {

                    // if contains scripts for DBs
                    if (dirs.IsRootDbDirectory(_config.PriorityDb))
                    {

                        if (dirs.Length > 1)
                        {
                            dirs = dirs.SortDirectoriesByPrioriry(_config.PriorityDb);
                        }

                        foreach (var dir in dirs)
                        {
                            ProcessRootDbDirectory(dir);
                            WriteLine(string.Format("\nDirectory \"{0}\" was processed successfully.", dir), ConsoleColor.Cyan);
                        }
                    }
                    else
                    {
                        if (dirs.Length > 1)
                        {
                            dirs = dirs.SortDirectoriesByPrioriry(_config.PriorityDirs);
                        }

                        foreach (var dir in dirs)
                        {
                            ProcessDbDirectory(dir);
                        }
                    }
                }
                else
                {
                    ProcessDbDirectory(directoryPath);
                }

                result = true;

                WriteLine(string.Format("\n____________________________________________________________\nDirectory \"{0}\" was processed successfully.", directoryPath), ConsoleColor.Yellow);
            }
            catch (Exception ex)
            {
                WriteLine(string.Format("\nDirectory: \"{0}\" was not processed.\nError:\n{1}\n", directoryPath, ex.Message), ConsoleColor.Red);
            }

            return result;
        }

        private void ProcessRootDbDirectory(string dbDirectory)
        {
            var dirs = Directory.GetDirectories(dbDirectory);
            dirs = dirs.SortDirectoriesByPrioriry(_config.PriorityDirs);

            foreach (var dir in dirs)
            {
                ProcessDbDirectory(dir);
            }

            // process root directory files
            var files = Directory.GetFiles(dbDirectory, SqlFileShearchPattern, SearchOption.TopDirectoryOnly);

            if (files.Length > 0) Console.WriteLine();

            foreach (var file in files)
            {
                ProcessFile(file);
            }
        }

        private void ProcessDbDirectory(string dbDirectory)
        {
            var files = Directory.GetFiles(dbDirectory, SqlFileShearchPattern, SearchOption.AllDirectories);

            if (files.Length > 0) Console.WriteLine();

            foreach (var file in files)
            {
                ProcessFile(file);
            }
        }

        private void ProcessFile(string fileName)
        {
            Process sqlProcess = null;

            try
            {
                sqlProcess = Process.Start(InitSqlCmdProcessInfo(fileName));

                sqlProcess.WaitForExit();

                WriteLine(string.Format("File \"{0}\" was processed successfully.", fileName));
            }
            catch (Exception ex)
            {
                WriteLine(string.Format("File: \"{0}\" was not processed.\nError:\n{1}", fileName, ex.Message), ConsoleColor.Red);
            }
            finally
            {
                if (sqlProcess != null)
                {
                    sqlProcess.Close();
                }
            }
        }

        private ProcessStartInfo InitSqlCmdProcessInfo(string fileName)
        {
            _startInfo.Arguments = string.Format(SqlCmdExecutePattern, _serverName, fileName);
            _startInfo.UseShellExecute = false;

            return _startInfo;
        }

        #region Console colors
        /// <summary>
        /// Writes an entire line to the console with the string and specific fore color.
        /// </summary>
        /// <param name="value">Text to output.</param>
        /// <param name="foregroundColor">Foreground color Green by default.</param>
        internal static void WriteLine(string value, ConsoleColor foregroundColor = ConsoleColor.Green)
        {
            Console.BackgroundColor = ConsoleColor.Black;
            Console.ForegroundColor = foregroundColor;
            Console.WriteLine(value.PadRight(Console.WindowWidth - 1)); // full line

            Console.ResetColor();
        }
        /// <summary>
        /// Writes the string with specific fore color.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="foregroundColor"></param>
        internal static void Write(string value, ConsoleColor foregroundColor = ConsoleColor.Green)
        {
            Console.BackgroundColor = ConsoleColor.Black;
            Console.ForegroundColor = foregroundColor;
            Console.Write(value); // full line

            Console.ResetColor();
        }
        #endregion

        #endregion

        #endregion
    }
}